Create a Real-Time Video Chat Room with WebRTC & Twilio

Course- Javascript >

To recap, WebRTC was developed to provide web and mobile developers with the ability to create high definition video and audio calls using simple APIs. Many large and small companies spanning all industries including, but not limited to, healthcare, education, customer care, professional services and social media are building next-gen applications utilizing WebRTC.

Chances are you have probably used this technology without even being aware of it. If you have used Facebook Messenger, WhatsApp and/or SnapChat video or voice features, you have unknowingly used WebRTC to communicate with your family and friends.

Jump Start Development

WebRTC and other similar web technologies are changing the way people communicate and engage in various ways. Developers are providing enhanced communications that integrate easily into any application. Just as companies like Facebook, SnapChat, Tango and WhatsApp are integrating live audio and video capabilities into their apps, so can you.

It may surprise you that the process for doing so is simple, quick and best of all cost efficient. Since this great technology was open-sourced by Google, you can now build your applications royalty free without licensing costs. However, the road to building your own solution can be quite daunting if you are not familiar with some common components required by WebRTC such as TURN/STUN, signaling, multipoint conferencing units (MCU) and so on.

In our industry, there are upwards of 20 platforms as a service (PaaS) providers offering WebRTC solutions. After gaining years of experience working with this technology, we (at Blacc Spot Media) have a few favorite PaaS providers that have proven to work well for our clients. We partner with many providers to shorten the development time required to get our clients’ products to market quickly. One of the providers we have had the opportunity to partner with is Twilio. In this article we are going to focus on their platform.

Twilio Video

If you are not familiar with Twilio, they provide a suite of communications tools through a set of simple APIs and SDKs. Their solution enables developers to add real-time communications capabilities to their apps. New to the Twilio arsenal is Programmable Video which allows you to create HD multi-party video and audio experiences in your mobile and web apps. Twilio is a veteran in the WebRTC industry and is expanding its current Twilio Client product, which already has some WebRTC components running at its core. Twilio Video has a bright future with a full roadmap of enhancements on the way. Soon you will be able to experience mobile screen sharing along with enhanced multi-party capabilities.

Building a Chat Room

You will need a Twilio account to use this demo. If you don’t have one you can sign up now for a free account. Make sure Your select “Programmable Video” as the first service you plan to use. Once you have created your account, you will need the following information to create your app:

Credentials Description
Twilio Account SID Your main Twilio account identifier – find it on your dashboard.
Twilio Video Configuration SID Adds video capability to the access token – generate one here
API Key Used to authenticate – generate one here.
API Secret Used to authenticate – just like the above, you’ll get one here.

We are also going to be using Firebase as a simple way to keep track of which users are available to chat. Firebase is a Mobile Backend as a Service that powers your app’s backend, including data storage, user authentication, static hosting, and more. If you don’t have an account, sign up for a free one to get started. Once you complete these instructions, you will be able to upload this demo to a server to run a live video chat room.

The Demo

The code developed in this article is available on GitHub, and you can view a live demo at Blacc Spot Media. Currently, WebRTC is only supported in Google Chrome, Mozilla Firefox, and Opera on the desktop:

 

If you are deploying this demo to a web server, it is important to note that as of Chrome 47, a SSL encrypted domain is required to gain access to a user’s microphone and camera. A simple way to solve this problem is to use the new Let’s Encrypt service to install a free certificate. You can find a good Tutorial on how to install the certificate on your server at Digital Ocean.

PHP

In order to gain access to the Twilio platform, you must first be authenticated in the system. In this example we are using PHP on the server side to get up and running quickly. Twilio has a wide array of server side libraries to fit your preference. After authentication occurs, we then pass the account credentials you received from your Twilio account above.

// ADD TWILIO REQURIED LIBRARIES
require_once('twilio/Services/Twilio.php');

// TWILIO CREDENTIALS
$TWILIO_ACCOUNT_SID = 'your account sid here';
$TWILIO_CONFIGURATION_SID = 'your configuration sid here';
$TWILIO_API_KEY = 'your API key here';
$TWILIO_API_SECRET = 'your API secret here';

// CREATE TWILIO TOKEN
// $id will be the user name used to join the chat
$id = $_GET['id'];

$token = new Services_Twilio_AccessToken(
    $TWILIO_ACCOUNT_SID,
    $TWILIO_API_KEY,
    $TWILIO_API_SECRET,
    3600,
    $id
);

// GRANT ACCESS TO CONVERSTATION
$grant = new Services_Twilio_Auth_ConversationsGrant();
$grant->setConfigurationProfileSid($TWILIO_CONFIGURATION_SID);
$token->addGrant($grant);

// JSON ENCODE RESPONSE
echo json_encode(array(
    'id'    => $id,
    'token' => $token->toJWT(),
));

HTML

The HTML code for this demo is quite simple and includes the ability to connect to the chat room using your name and see a full list of users that are available to chat as well as an event log.

<div class="m-content">
    <h1>Quick Start Your WebRTC Project with Twilio</h1>
    <div class="start">
        <input type="text" id="id" name="id" value="" placeholder="Enter your name to join the chat" />
        <button id="start">Join Chat Room</button>
        <button id="disconnect" class="b-disconnect hide">Disconnect from Chat</button>
        <div class="status">
            <strong>MY STATUS:</strong> <span id="status">DISCONNECTED</span>
        </div>
    </div>
    <div class="local">
        <div id="lstream"></div>
    </div>
    <div class="remote">
        <div id="rstream"></div>
    </div>
    <div class="users-list"></div>
    <div class="logs"></div>
</div>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
<script src="https://media.twiliocdn.com/sdk/js/conversations/v0.13/twilio-conversations.min.js"></script>
<script src="https://cdn.firebase.com/js/client/2.4.0/firebase.js"></script>
<script src="app.js"></script>

JavaScript

First things first. It is important to ensure that the user is connecting by using a compatible WebRTC browser.

if (!navigator.webkitGetUserMedia && !navigator.mozGetUserMedia) {
    tlog('You are using a browser that is not WebRTC compatible, please use Google Chrome or Mozilla Firefox', true);
}

We’re using a function called tlog() to send status messages to the user. We send a HTML string to the msg parameter along with setting e to true if there is an error that occurs.

function tlog(msg, e) {
  if (e) {
    $('.logs').append('<div class="log error">' + msg + '</div>');
  } else {
    $('.logs').append('<div class="log">' + msg + '</div>');
  }
}

Now that we know the user is connecting using a compatible browser, we will use jQuery in order to add a click event when the user is ready to join the chat. We will then send an Ajax request to our server side token.php generation file with the id of the user connecting to the chat room.

In this demo we are using the name the user enters into the textfield. After we have received our token, we pass it along to the Twilio.AccessManager to create a new Twilio.Conversations.Client that will provide us with a set of event listeners using a Promise.

$('#start').on('click', function() {
  if ($('#id').val() == '') {
    tlog('Please enter a name to join the chat', true);
  } else {
    id = $('#id').val().replace(/\s+/g, '');
    $.ajax({
      type: 'POST',
      url: 'token.php',
      data: {
        id: $('#id').val()
      },
      dataType: "json",
      success: function(data) {

        /* We pass the provided access token to the accessManager */
        var accessManager = new Twilio.AccessManager(data.token);
        conversationsClient = new Twilio.Conversations.Client(accessManager);
        conversationsClient.listen().then(clientConnected, function(e) {
          tlog('Could not connect to Twilio: ' + e.message, true);
        });
      }
    });
  }
});

After we have received a successfully connected response from the new Twilio.Conversations.Client, we call the clientConnected() function along with firebaseConnect(). You will need to add the Firebase url from your account to store all available users. We will also add some event listeners for Firebase to track when users connect to and leave the chat.

function clientConnected() {
  firebaseConnect();
  $('#id, #start').hide();
  $('#disconnect').fadeIn();
  $('#status').css({ 'color': '#5E9F21' }).text('CONNECTED');
  tlog('You have succussfully connected to this Twilio chat room as <strong>' + id + '</strong>.');
  if (!lmedia) {
    startConversation();
  };
  conversationInvite();
}

function firebaseConnect(){
  var fburl = '...add your firebase url here...';
  firebase = new Firebase(fburl + '/users');
  var uid = firebase.push(id);
  fid = uid.toString();

  new Firebase(fid)
    .onDisconnect()
    .remove();

  firebase.on('child_added', function(child) {
    addParticipant(child);
  });

  firebase.on('child_removed', function(child) {
    $('.' + child.val()).remove();
  });
}

function addParticipant(child) {
  if (child.val() != id) {
    var markup = '<div class="user ' + child.val() + '"><span>'
               + child.val() + '</span><button class="b-connect" id="'
               + child.val() + '">Call Now</button></div>';
    $('.users-list').append(markup);
  }
}

Now for the magic of WebRTC! We gain access to the user’s microphone and camera by calling new Twilio.Conversations.LocalMedia() and attaching the stream to the HTML element. Please Note: You will need to grant access to your microphone and camera.

function startConversation() {
  lmedia = new Twilio.Conversations.LocalMedia();
  Twilio.Conversations.getUserMedia().then(function(mediaStream) {
    lmedia.addStream(mediaStream);
    lmedia.attach('#lstream');
  }, function(e) {
    tlog('We were unable to access your Camera and Microphone.');
  });
}

Next we add an event listener for incoming invites from other users in the chat room. We have added invite.accept().then(conversationStarted) to allow automatic connection. In your application you may want to prompt the other user first before connecting. Please Note: You will need to allow access to your camera and microphone for each additional user.

function conversationInvite() {
  conversationsClient.on('invite', function(invite) {
    invite.accept().then(conversationStarted);
    tlog('You have a incoming invite from: <strong>' + invite.from + '</strong>');
  });
}

Once you have joined the chat, you will see a Call Now button next to each user that is available to chat in the chat room. Click on the button to connect to the user.

We will pass our current local media stream as options.localMedia to the person we are inviting to chat.

$(document).on('click', '.b-connect', function() {
    var user = $(this).attr('id');
    var options = {};
    options.localMedia = lmedia;
    conversationsClient
      .inviteToConversation(user, options)
      .then(conversationStarted, function(error) {
        tlog('We were unable to create the chat conversation with that user, try another online user.', true);
      });
});

After accepting the incoming invite, we will be connected to start a conversation.

function conversationStarted(convo) {
  conversation = convo;
  tlog('We are waiting on your friend to connect...');
  participantConnected();
  participantDisconnected();
}

Next we add the participantConnected() function with an event listener for all participants connecting to the conversation. As the other user joins, we attach their media using participant.media.attach('#rstream').

function participantConnected() {
  conversation.on('participantConnected', function(participant) {
    new Firebase(fid).remove();
    participant.media.attach('#rstream');
    tlog('You are connected with: <strong>' + participant.identity + '</strong>');
  });
}

Lastly, we add an event listener for all participants disconnecting from the conversation in the participantDisconnected() function. It allows us to track any disconnects and remove users from the chat room successfully.

function participantDisconnected() {
  conversation.on('participantDisconnected', function(participant) {
    if (!disconnected) {
      var uid = firebase.push(id);
      fid = uid.toString();
      new Firebase(fid).onDisconnect().remove();
    }

    $('.' + participant.identity).remove();
    tlog('<strong>' + participant.identity + '</strong> has disconnected from this chat.');
    $('.users-list').empty();

    if (firebase) {
      firebase.once('child_added', function(child) {
        addParticipant(child);
      });
    }
  });
}

We offer users a way to manually disconnect from the chat room and reset to the default state. We clear the user’s availability from Firebase so other users will not be able to connect to until the user comes back online. Next we stop the streaming of media to the conversation and stop sharing access to our microphone and camera.

$('#disconnect').on('click', function() {
  new Firebase(fid).remove();
  firebase.off();
  firebase = null;
  disconnected = true;
  $('#disconnect').hide();
  $('#start, #id').fadeIn();
  $('#status').css({
    'color': ''
  }).text('DISCONNETED');
  $('.users-list').empty();
  stopConversation();
});

function stopConversation() {
  if (conversation) {
    conversation.disconnect();
    conversationsClient = null;
    conversation = null;
    lmedia.stop();
    lmedia = null;
    tlog('You have successfully disconnected from this chat conversation, start another one now.');
  } else {
    lmedia.stop();
    lmedia = null;
    tlog('Please rejoin the chatroom to start a conversation.');
  }
}